package com.work.mybatis.factory;

import com.work.mybatis.handler.*;
import com.work.mybatis.mapper.Param;
import com.work.mybatis.mapper.Select;
import com.work.mybatis.mapper.TypeHandler;
import com.work.mybatis.mapper.UserMapper;

import java.lang.reflect.*;
import java.sql.*;
import java.util.*;

public class MapperProxyFactory {
    private static Map<Class, TypeHandler> typeHandlerMap = new HashMap<>();
    static {
        typeHandlerMap.put(String.class, new StringTypeHandler());
        typeHandlerMap.put(Integer.class,new IntegerTypeHandler());
    }
    static {
        try {
            Class.forName("com.mysql.cj.jdbc.Driver");
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
    public static <T> T getMapper(Class<T> mapper) {
        Object proxyInstance= Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(), new Class[]{mapper}, new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                return doInvoke(proxy, method, args);
            }
        });
        return (T) proxyInstance;
    }
    public static Object doInvoke  (Object proxy, Method method, Object[] args) throws Throwable {
        //返回的对象
        Object result=null;
        //List类型呢的对象
        List<Object> list=new ArrayList<Object>();

        //动态代理执行方法
        //获取连接----获取session----获取sql----获取参数----执行sql----返回数据
        Connection conn = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/db1?useUnicode=true&characterEncoding=utf8&autoReconnect=true&serverTimezone=UTC","root","123456");

        //拿到方法注解
        // 类似 @Select("SELECT * FROM `user` WHERE name = #{name} and age = #{age}")
        Select annotation = method.getAnnotation(Select.class);

        //sql = select * from user where name = #{name} and age = #{age} and id = #{name}
        String sql = annotation.value();
//        System.out.println(sql);
        //使用map记录要修改的参数名和参数
        //name=1 age=2等等
        Map<String, Object> paramMap = new HashMap<String, Object>();

        //获取该方法传入的所有参数
        Parameter[] parameters = method.getParameters();

        //
            for (int i = 0; i < parameters.length; i++) {
                Parameter parameter = parameters[i];
                //这里name不是真的字段名
                paramMap.put(parameter.getName(), args[i]);
//                System.out.println(parameter.getName());
                //利用parm注解拿到参数名
                String parmName = parameter.getAnnotation(Param.class).value();
//                System.out.println(parmName);
                paramMap.put(parmName, args[i]);
            }


            //SQL语句中的#{}去掉改成？
            //
            ParameterMappingTokenHandler tokenHandler = new ParameterMappingTokenHandler();
            GenericTokenParser parse = new GenericTokenParser("#{", "}", tokenHandler);
            String parseSql = parse.parse(sql);


            //存放替换的参数名 如name age name
            List<ParameterMapping> parameterMappings = tokenHandler.getParameterMappings();


            //写入SQL语句
        PreparedStatement preparedStatement = conn.prepareStatement(parseSql);
            //赋值
            for (int i = 0; i < parameterMappings.size(); i++) {
                String property = parameterMappings.get(i).getProperty();

                //注意这里赋值的时候，会有个类型的问题，有可能是字符串，也有可能是数字类型，所以需要知道参数的类型，然后才能给参数赋值，不然会报错
                //stmt.setString(); ???
                //stmt.setInt(); ???


                //通过变量名获取变量的类型
                Object value = paramMap.get(property);
                Class<?> type = value.getClass();

                //利用类型处理器，根据字段类型来给变量赋值
                typeHandlerMap.get(type).setParameter(preparedStatement, i + 1, value);
            }
                preparedStatement.execute();

                //获取结果
                ResultSet resultSet = preparedStatement.getResultSet();





      //          *** 下面判断结果集返回的是基础类型int还是对象user ***         //
        //这里要判断返回结果是否为空，如果不是select，是insert或者update，delete的话就没有 返回结果集
        if(resultSet != null){
            Class resultType =null;
            Type genericReturnType = method.getGenericReturnType();
            if(genericReturnType instanceof Class){
                //不是泛型
                resultType = (Class) genericReturnType;

            }else if(genericReturnType instanceof ParameterizedType){
                //是泛型,这里只考虑list<User> 这种简单泛型，不考虑 List<User> 这种
                Type[] actualTypeArguments = ((ParameterizedType) genericReturnType).getActualTypeArguments();
                //取泛型的第一个List<User>
                resultType = (Class) actualTypeArguments[0];
            }

            ResultSetMetaData metaData = resultSet.getMetaData();

            //数据库中的字段名 id name age
            List<String> columnList = new ArrayList<>();
            for(int i=0; i<metaData.getColumnCount(); i++){
                columnList.add(metaData.getColumnLabel(i+1));
            }

            //记录User对象中的set方法
            Map<String,Method> setterMethodMapping = new HashMap<>();

            for(Method declaredMethod : resultType.getDeclaredMethods()){
                if(declaredMethod.getName().startsWith("set")){
                    String methodName = declaredMethod.getName().substring(3);
                    methodName = methodName.substring(0,1).toLowerCase(Locale.ROOT) + methodName.substring(1);
                    setterMethodMapping.put(methodName,declaredMethod);
                }
            }

            //循环结果集导入user类
             while (resultSet.next()){
                 Object instance = resultType.newInstance();
                 for (int i = 0; i < columnList.size(); i++) {
                     //数据库字段名
                     String columnName = columnList.get(i);
                  //
                     if(setterMethodMapping.size()>0){
                         Method setterMethod = setterMethodMapping.get(columnName);
                         //然后通过setter方法找到入参的类型，因为setter方法只有1个参数，所以取0个，渠道的结果是String,Int或者其他类型
                         Class clazz = setterMethod.getParameterTypes()[0];

                         //然后根据入参的类型来调用具体的处理器，意思就是到底是调用setString,还是setInt，还是别的类型
                         TypeHandler typeHandler = typeHandlerMap.get(clazz);

                         //获取结果集里面字段对应的值
                         Object resultValue = typeHandler.getResult(resultSet, columnName);

                         //setter方法执行（2个参数，一个是对象，一个是执行setter方法的参数值）,这样User对象里的属性就有值了
                         setterMethod.invoke(instance,resultValue);

                     }
                     else{
                         //没有set方法，返回基本数据类型
                         //获取结果集里面字段对应的值
                         Class clazz  = (Class)method.getGenericReturnType();
                         //然后根据入参的类型来调用具体的处理器，意思就是到底是调用setString,还是setInt，还是别的类型
                         TypeHandler typeHandler = typeHandlerMap.get(clazz);

                         instance = typeHandler.getResult(resultSet, columnName);
                     }

                 }
                 list.add(instance);
             }


            }
        else {
            result=preparedStatement.getUpdateCount();
        }

//这里不能直接返回list,需要根据方法返回的类型来判断 到底是返回list,还是对象，还是其他类型
        if(method.getReturnType().equals(List.class)){
            //如果是list，就直接返回
            result = list;
        }else if(method.getReturnType().equals(Object.class)){
            //如果是对象 就取第一个
            result = list.get(0);
        }else{
            //其他类型，insert update delete只需要返回 更新的条数

            if(list.size() > 0){
                result = list.get(0);
            }
        }
        if(preparedStatement != null){
            try {
                preparedStatement.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
        if(resultSet != null){
            try {
                resultSet.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
            return result;
    }
}
